﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Net;
using System.Reflection;
using Ping.Core;
using log4net;

namespace Ping.Server
{
	/// <summary>
	/// A small in-memory cache for geo-data-sets.
	/// </summary>
	public sealed class GeoCache
	{
		private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

		private readonly Dictionary<IPAddress, CacheLine> _cache;

		public GeoCache()
		{
			_cache = new Dictionary<IPAddress, CacheLine>();
		}

		/// <summary>
		/// Geolocates the given IP-Address.
		/// </summary>
		/// <param name="address"></param>
		/// <returns>The result of the geolocation or null</returns>
		public GeoDataSet Lookup(IpAddress address)
		{
			if (address == null)
				throw new ArgumentNullException();
			if (!address.IsValid)
				throw new ArgumentException();

			return Lookup(address.Native);
		}

		/// <summary>
		/// Geolocates the given IP-Address.
		/// </summary>
		/// <param name="address"></param>
		/// <returns>The result of the geolocation or null</returns>
		public GeoDataSet Lookup(IPAddress address)
		{
			if (address == null)
				throw new ArgumentNullException();

			lock (_cache)
			{
				CacheLine tmp;
				if (!_cache.TryGetValue(address, out tmp))
					return null;

				if (Log.IsDebugEnabled)
					Log.DebugFormat("Found GeoDataSet for {0} in cache", address);

				return tmp.Data;
			}
		}

		public void Store(IpAddress address, GeoDataSet dataSet)
		{
			if (address == null)
				throw new ArgumentNullException();
			if (!address.IsValid)
				throw new ArgumentException();

			Store(address.Native, dataSet);
		}

		public void Store(IPAddress address, GeoDataSet dataSet)
		{
			if (address == null || dataSet == null)
				throw new ArgumentNullException();

			lock (_cache)
			{
				if (Log.IsDebugEnabled)
					Log.DebugFormat("Storing GeoDataSet for {0} in cache", address);

				_cache[address] = new CacheLine(dataSet);
			}
		}
	}

	internal struct CacheLine
	{
		public CacheLine(GeoDataSet data)
		{
			Data = data;
			Time = DateTime.Now;
		}

		public readonly GeoDataSet Data;
		public readonly DateTime Time;
	}
}